package com.wolfeyes.framework.config;

import com.wolfeyes.common.utils.framework.spring.SpringContextUtils;
import com.wolfeyes.framework.service.CustomizeUserDetailsService;
import com.wolfeyes.framework.springsecurity.filter.CustomizeJwtAuthenticationLoginFilter;
import com.wolfeyes.framework.springsecurity.filter.CustomizeJwtAuthenticationTokenFilter;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAbstractSecurityInterceptor;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAccessDecisionManager;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAccessDeniedHandler;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAuthenticationEntryPoint;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAuthenticationFailureHandler;
import com.wolfeyes.framework.springsecurity.handle.CustomizeAuthenticationSuccessHandler;
import com.wolfeyes.framework.springsecurity.handle.CustomizeFilterInvocationSecurityMetadataSource;
import com.wolfeyes.framework.springsecurity.handle.CustomizeLogoutSuccessHandler;
import com.wolfeyes.framework.springsecurity.handle.CustomizeSessionInformationExpiredStrategy;

import lombok.RequiredArgsConstructor;

//import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
//import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

/**
 * @description SpringSecurity安全认证权限框架配置信息
 * @see Talk is cheap, Show me the code. -- Linus Torvalds
 * @title 配置SpringSecurity安全框架
 * @author yanyljava
 * @create 2022-01-14 21:25
 */
@RequiredArgsConstructor
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 控制@Secured权限注解
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

	/**
	 * @title 这里需要交给spring注入,而不是直接new
	 * @description 自定义用户认证逻辑-获取用户账号密码及权限信息
	 */
	//@Autowired
	private final CustomizeUserDetailsService customizeUserDetailsService;

	/**
	 * @title 登录过滤器的配置
	 */
	private final JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
	
	/**
	 * @description token校验认证过滤器
	 */
	//@Autowired
	private final CustomizeJwtAuthenticationTokenFilter customizeJwtAuthenticationTokenFilter;
	
	/**
	 * @description 跨域过滤器
	 */
	//@Autowired
	private final CorsFilter corsFilter;
	
	/**
	 * @description 身份认证（失败）入口点（切入点）处理类-unauthorized Handler-匿名用户访问无权限资源（需要登录）
	 */
	//@Autowired
	private final CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
	
	/**
	 * @description 登录成功处理逻辑处理器
	 */
	//@Autowired
	private final CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;

	/**
	 * @description 登录失败处理逻辑处理器
	 */
	//@Autowired
	private final CustomizeAuthenticationFailureHandler customizeAuthenticationFailureHandler;
	
	/**
	 * @description 登录后权限不足拒绝访问逻辑处理器
	 */
	private final CustomizeAccessDeniedHandler customizeAccessDeniedHandler;
	
	/**
	 * @description 登出注销（成功）退出处理器
	 */
	//@Autowired
	private final CustomizeLogoutSuccessHandler customizeLogoutSuccessHandler;

	/**
	 * @description 会话失效(账号被挤下线)逻辑处理器-自定义会话信息过期策略
	 */
	private final CustomizeSessionInformationExpiredStrategy customizeSessionInformationExpiredStrategy;
	
	/**
	 * @description 访问决策管理器
	 */
	//@Autowired
	private final CustomizeAccessDecisionManager customizeAccessDecisionManager;
	
	/**
	 * @description 自定义安全元数据的数据源
	 */
	//@Autowired
	private final CustomizeFilterInvocationSecurityMetadataSource customizeFilterInvocationSecurityMetadataSource;
	
	/**
	 * @description 权限拦截器-实现权限拦截
	 */
	//@Autowired
	private final CustomizeAbstractSecurityInterceptor customizeAbstractSecurityInterceptor;
	
	
	/**
	 * @description 解决 无法直接注入 AuthenticationManager
	 * @return AuthenticationManager
	 * @throws Exception 异常
	 */
	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		
		return super.authenticationManagerBean();
	}

	/**
	 * anyRequest          |   匹配所有请求路径
	 * access              |   SpringEl表达式结果为true时可以访问
	 * anonymous           |   匿名可以访问（所有人都能访问，但是带上 token 访问后会报错）
	 * denyAll             |   用户不能访问
	 * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）
	 * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问
	 * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问
	 * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问
	 * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
	 * hasRole             |   如果有参数，参数表示角色，则其角色可以访问
	 * permitAll           |   用户可以任意访问（所有人都能访问，包括带上 token 访问）
	 * rememberMe          |   允许通过remember-me登录的用户访问
	 * authenticated       |   用户登录后可访问
	 */
	@Override
	protected void configure(HttpSecurity httpSecurity) throws Exception {
		
		// http相关的配置，包括登入登出、异常处理、会话管理等
		httpSecurity
				// CSRF禁用，因为不使用session
				.csrf().disable()
				// //异常处理(权限拒绝、登录失效等)
				.exceptionHandling()
				//匿名用户访问无权限资源时的异常处理（认证未通过-匿名未登录，不允许访问的异常处理器）
				.authenticationEntryPoint(customizeAuthenticationEntryPoint)
				//登录后权限拒绝处理逻辑，认证失败处理类（认证通过-已登录，但是没权限或权限不足的处理器）
				.accessDeniedHandler(customizeAccessDeniedHandler)
				.and()
				// 配置取消session管理,由Jwt来获取用户状态,否则即使token无效,也会有session信息,依旧判断用户为登录状态
				// 基于token，所以不需要session
				//请注意：如果配置了下面的SessionCreationPolicy.STATELESS，则SpringSecurity不会保存session会话，在/logout登出的时候会拿不到用户实体对象。
				//如果登出注销不依赖SpringSecurity，并且session交给redis的token来管理的话，可以按下面的配置
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
				.and()
				// 过滤请求，设置URL的授权
				.authorizeRequests()
				// 对于登录login 验证码captchaImage 允许匿名访问
				.antMatchers("/login", "/captchaImage").anonymous()
				// POST请求并且是 请求`/login` 接口则可以直接通过
				//.antMatchers(HttpMethod.POST, "/login").permitAll()
				.antMatchers(
						HttpMethod.GET,
						"/*.html",
						"/**/*.html",
						"/**/*.css",
						"/**/*.js"
				// 使用 permitAll() 方法所有人都能访问，包括带上 token 访问
				).permitAll()
				// 使用 anonymous() 所有人都能访问，但是带上 token 访问后会报错
				// 配置匿名访问时，匿名访问方法@PreAuthorize权限注解也需要去掉。
				.antMatchers("/profile/**").anonymous()
				.antMatchers("/common/download**").anonymous()
				.antMatchers("/common/download/resource**").anonymous()
				.antMatchers("/swagger-ui.html").anonymous()//SWAGGER_WHITELIST swagger路径白名单
				.antMatchers("/swagger-ui/*").anonymous()
				.antMatchers("/v2/api-docs").anonymous()
				.antMatchers("/v3/api-docs").anonymous()
				.antMatchers("/webjars/**").anonymous()
				.antMatchers("/swagger-resources/**").anonymous()
				.antMatchers("/webjars/**").anonymous()
				.antMatchers("/*/api-docs").anonymous()
				.antMatchers("/druid/**").anonymous()
				//.antMatchers("/sysUser/**").anonymous()//放行
				// 使用 permitAll() 方法所有人都能访问，包括带上 token 访问
				.antMatchers("/sysUser/**").permitAll()
				// 使用 permitAll() 方法所有人都能访问，包括带上 token 访问
				.antMatchers("/alipay/**").permitAll()
				.antMatchers("/images/**").anonymous()//放行图片查询
				.antMatchers("/captcha/**").anonymous()//放行验证码请求
				.antMatchers("/error/**").anonymous()//错误✖放行
				// 除上面外的所有请求全部需要鉴权认证
				// 这里表示任何请求都需要校验认证(上面配置的放行)
				.anyRequest().authenticated()
				.and()
				.headers().frameOptions().disable()
				.and()
				// 配置登录,检测到用户未登录时跳转的url地址,登录放行
				.formLogin()
				// 需要跟前端表单的action地址一致
				//.loginProcessingUrl("/login")
				//.successHandler(customizeAuthenticationSuccessHandler)
				//.failureHandler(customizeAuthenticationFailureHandler)
				//.permitAll()
				//禁用表单登录，前后端分离用不上
				.disable()
				//.and()
				//会话管理
				.sessionManagement()
				//同一账号同时登录最大用户数
				.maximumSessions(1)
				//会话失效(账号被挤下线)逻辑处理器
				.expiredSessionStrategy(customizeSessionInformationExpiredStrategy);
		// 配置登出,登出放行
		httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(customizeLogoutSuccessHandler).permitAll();
		// 应用登录过滤器的配置，配置分离-CustomizeJwtAuthenticationLoginFilter
		httpSecurity.apply(jwtAuthenticationSecurityConfig);
		//httpSecurity.addFilterBefore(new CustomizeJwtAuthenticationLoginFilter("/login",super.authenticationManager()),UsernamePasswordAuthenticationFilter.class);
		// 添加自定义的JWT filter，解析前端发起的请求中是否有token
		//将TOKEN校验过滤器配置到过滤器链中，否则不生效，放到UsernamePasswordAuthenticationFilter之前
		httpSecurity.addFilterBefore(customizeJwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
		// 添加CORS filter
		httpSecurity.addFilterBefore(corsFilter, CustomizeJwtAuthenticationTokenFilter.class);
		httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
	}

	/**
	 * @description 在SpringSecurity配置类中指定自定义密码凭证匹配器
	 * @description 如需使用自定义密码凭证匹配器 返回自定义加密对象注入IOC容器
	 * @title 强散列哈希加密实现
	 * @return 加密对象（自定义密码凭证匹配器），例如: return new MD5PasswordEncoder(); 
	 */
	@Bean
	public BCryptPasswordEncoder bCryptPasswordEncoder() {
		// 设置默认的加密方式（强hash方式加密）
		//return new BCryptPasswordEncoder();
		//return new ZqwBCryptPasswordEncoder();
    	return new BCryptPasswordEncoder() {
    		@Override
    		public boolean matches(CharSequence rawPassword, String encodedPassword) {
    			// 开发环境[dev]开启免密模式
    			if ("dev".equalsIgnoreCase(SpringContextUtils.getActiveProfile())) {
    				return true;
    			}
    			boolean matcheResult = super.matches(rawPassword, encodedPassword);
    			return matcheResult;
    		}
    	};
	}

	/**
	 * @title 身份认证接口
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
		// 配置认证方式等
		authenticationManagerBuilder.userDetailsService(customizeUserDetailsService).passwordEncoder(bCryptPasswordEncoder());
	}

}


